[Previous] [Next]

Scope and Lifetime of Variables

Not all variables are born equal. Certain variables live for the entire life of the application, and others are created and destroyed a thousand times every second. A variable could be visible only from within a procedure or a module or could be in existence only during well-defined time windows over the lifetime of the application. To better define these concepts, I need to introduce two formal definitions.

Global Variables

In Visual Basic jargon, global variables are those variables declared using the Public keyword in BAS modules. Conceptually, these variables are the simplest of the group because they survive for the life of the application and their scope is the entire application. (In other words, they can be read and modified from anywhere in the current program.) The following code snippet shows the declaration of a global variable:

' In a BAS module
Public InvoiceCount as Long    ' This is a global variable.

Visual Basic 6 still supports the Global keyword for backward compatibility with Visual Basic 3 and previous versions, but Microsoft doesn't encourage its use.

In general, it's a bad programming practice to use too many global variables. If possible, you should limit yourself to using module-level or local variables because they allow easier code reuse. If your modules and individual routines rely on global variables to communicate with each other, you can't reuse such code without also copying the definitions of the involved global variables. In practice, however, it's often impossible to build a nontrivial application without using global variables, so my suggestion is this: Use them sparingly and choose for them names that make their scope evident (for example, using a g_ or glo prefix). Even more important, add clear comments stating which global variables are used or modified in each procedure:

' NOTE: this procedure depends on the following global variables:
'     g_InvoiceCount  : number of invoices (read and modified)
'     g_UserName      : name of current user (read only)
Sub CreateNewInvoice()
    ...
End Sub

An alternative approach, which I often find useful, is to define a special GlobalUDT structure that gathers all the global variables of the application and to declare one single global variable of type GlobalUDT in one BAS module:

' In a BAS module
Public Type GlobalUDT
    InvoiceCount As Long
    UserName As String
    ....
End Type
Public glo As GlobalUDT

You can access these global variables using a very clear, unambiguous syntax:

' From anywhere in the application
glo.InvoiceCount = glo.InvoiceCount + 1

This technique has a number of advantages. First, the scope of the variable is evident by its name. Then if you don't remember the name of your variable, you can just type the three characters glo, and then type the dot and let Microsoft IntelliSense show you the list of all the components. In most cases, you just need to type a few characters and let Visual Basic complete the name for you. It's a tremendous time saver. The third advantage is that you can easily save all your global variables to a data file:

' The same routine can save and load global data in GLO.
Sub SaveLoadGlobalData(filename As String, Save As Boolean)
    Dim filenum As Integer, isOpen As Boolean
    On Error Goto Error_Handler
    filenum = FreeFile
    Open filename For Binary As filenum
    isOpen = True
    If Save Then
        Put #filenum, , glo
    Else
        Get #filenum, , glo
    End If
Error_Handler:
    If isOpen Then Close #filenum
End Sub

The beauty of this approach is that you can add and remove global variables—actually, components of the GlobalUDT structure—without modifying the SaveLoadGlobalData routine. (Of course, you can't correctly reload data stored with a different version of GlobalUDT.)

Module-Level Variables

If you declare a variable using a Private or a Dim statement in the declaration section of a module—a standard BAS module, a form module, a class module, and so on—you're creating a private module-level variable. Such variables are visible only from within the module they belong to and can't be accessed from the outside. In general, these variables are useful for sharing data among procedures in the same module:

' In the declarative section of any module
Private LoginTime As Date     ' A private module-level variable
Dim LoginPassword As String   ' Another private module-level variable

You can also use the Public attribute for module-level variables, for all module types except BAS modules. (Public variables in BAS modules are global variables.) In this case, you're creating a strange beast: a Public module-level variable that can be accessed by all procedures in the module to share data and that also can be accessed from outside the module. In this case, however, it's more appropriate to describe such a variable as a property:

' In the declarative section of Form1 module
Public CustomerName As String          ' A Public property

You can access a module property as a regular variable from inside the module and as a custom property from the outside:

' From outside Form1 module...
Form1.CustomerName = "John Smith"

The lifetime of a module-level variable coincides with the lifetime of the module itself. Private variables in standard BAS modules live for the entire life of the application, even if they can be accessed only while Visual Basic is executing code in that module. Variables in form and class modules exist only when that module is loaded in memory. In other words, while a form is active (but not necessarily visible to the user) all its variables take some memory, and this memory is released only when the form is completely unloaded from memory. The next time the form is re-created, Visual Basic reallocates memory for all variables and resets them to their default values (0 for numeric values, "" for strings, Nothing for object variables).

Dynamic Local Variables

Dynamic local variables are defined within a procedure; their scope is the procedure itself, and their lifetime coincides with that of the procedure:

Sub PrintInvoice()
    Dim text As String      ' This is a dynamic local variable.
    ...
End Sub

Each time the procedure is executed, a local dynamic variable is re-created and initialized to its default value (0, an empty string, or Nothing). When the procedure is exited, the memory on the stack allocated by Visual Basic for the variable is released. Local variables make it possible to reuse code at the procedure level. If a procedure references only its parameters and its local variables (it relies on neither global nor module-level variables), it can be cut from one application and pasted into another without any dependency problem.

Static Local Variables

Static local variables are a hybrid because they have the scope of local variables and the lifetime of module-level variables. Their value is preserved between calls to the procedure they belong to until their module is unloaded (or until the application ends, as is the case for procedures inside standard BAS modules). These variables are declared inside a procedure using the Static keyword:

Sub PrintInvoice()
    Static InProgress As Boolean   ' This is a Static local variable.
    ...
End Sub

Alternatively, you can declare the entire procedure to be Static, in which case all variables declared inside it are considered to be Static:

Static Sub PrintInvoice()
    Dim InProgress As Boolean      ' This is a Static local variable.
    ...
End Sub

Static local variables are similar to private module-level variables, to the extent that you can move a Static declaration from inside a procedure to the declaration section of the module (you only need to convert Static to Dim, because Static isn't allowed outside procedures), and the procedure will continue to work as before. In general, you can't always do the opposite: Changing a module-level variable into a Static procedure-level variable works if that variable is referenced only inside that procedure. In a sense, a Static local variable is a module-level variable that doesn't need to be shared with other procedures. By keeping the variable declaration inside the procedure boundaries, you can reuse the procedure's code more easily.

Static variables are often useful in preventing the procedure from being accidentally reentered. This is frequently necessary for event procedures, as when, for example, you don't want to process user clicks of the same button until the previous click has been served, as shown in the code below.

Private Sub cmdSearch_Click()
    Static InProgress As Boolean
    ' Exit if there is a call in progress.
    If InProgress Then MsgBox "Sorry, try again later": Exit Sub
    InProgress = True
    ' Do your search here.
      ...
    ' Then reenable calls before exiting.
    InProgress = False
End Sub